package com.atguigu.gmall.activity.listener;

import com.atguigu.gmall.activity.mapper.SeckillGoodsMapper;
import com.atguigu.gmall.activity.redis.CacheHelper;
import com.atguigu.gmall.activity.util.DateUtil;
import com.atguigu.gmall.common.constant.MqConst;
import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.model.activity.SeckillGoods;
import com.atguigu.gmall.model.activity.UserRecode;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.rabbitmq.client.Channel;
import io.lettuce.core.dynamic.annotation.Key;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.Date;
import java.util.List;

/**
 * 监听器
 */
@Component
public class ActivityListener {

    @Autowired
    private SeckillGoodsMapper seckillGoodsMapper;
    @Autowired
    private RedisTemplate redisTemplate;

    //秒杀商品压入Redis
    @RabbitListener(bindings = {@QueueBinding(
            exchange = @Exchange(value = MqConst.EXCHANGE_DIRECT_TASK),
            value = @Queue(value = MqConst.QUEUE_TASK_1, durable = "true", autoDelete = "false"),
            key = MqConst.ROUTING_TASK_1
    )})
    public void findSeckKillGoodsToRedis(Message message, Channel channel) {
        try {
            //1:查询DB中的秒杀商品
            QueryWrapper<SeckillGoods> seckillGoodsQueryWrapper = new QueryWrapper<>();
            //1.1:秒杀的商品必须是审核通过的
            seckillGoodsQueryWrapper.eq("status", "1");
            //1.2:秒杀的商品必须有剩余库存
            seckillGoodsQueryWrapper.gt("stock_count", 0);
            //1.3:秒杀的商品必须是活动范围之内(只秒杀当天的数据)
            seckillGoodsQueryWrapper.eq("DATE(start_time)", DateUtil.formatDate(new Date()));
            List<SeckillGoods> seckillGoodsList = seckillGoodsMapper.selectList(seckillGoodsQueryWrapper);
            //2.将查询的秒杀商品压入缓存中 Hash类型 就是个map 保存对象的 h hk hv
            for (SeckillGoods seckillGoods : seckillGoodsList) {
                //2.1 判断此商品是否已经在缓存了  用来看的
                if (!redisTemplate.opsForHash().hasKey(RedisConst.SECKILL_GOODS, seckillGoods.getSkuId().toString())) {
                    //参数1 h String类型
                    //参数2 hk 库存ID skuId String类型
                    //参数3 hv 任意类型 POJO必须实现序列化接口
                    redisTemplate.opsForHash().put(RedisConst.SECKILL_GOODS, seckillGoods.getSkuId().toString(), seckillGoods);
                    //3.防止超卖 放的应该是实体skuId而不是数量skuNum 用来卖的
                    //现在的结构如  [18 18 18 18 18 18 18 18 18 18]  每卖出一个弹出一个18
                    String[] skuIds = buildSkuIdArray(seckillGoods);
                    redisTemplate.opsForList().leftPushAll(RedisConst.SECKILL_STOCK_PREFIX
                            + seckillGoods.getSkuId(), skuIds);
                    //4.状态位 由Redis发给多个微服务 存储在每个微服务的本地缓存 本地缓存比分布式缓存快 但浪费一丝丝空间
                    redisTemplate.convertAndSend("seckillpush", seckillGoods.getSkuId() + ":1");
                }
            }
            //应答
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //准备商品数量个商品id  如10件商品则需要10个商品id的数组
    private String[] buildSkuIdArray(SeckillGoods seckillGoods) {
        Integer stockCount = seckillGoods.getStockCount();
        String[] ids = new String[stockCount];
        for (Integer i = 0; i < stockCount; i++) {
            ids[i] = seckillGoods.getSkuId().toString();
        }
        return ids;
    }

    /**
     * 秒杀
     */
    //监听开始抢购  必须使用MQ削峰
    @RabbitListener(bindings = {@QueueBinding(
            exchange = @Exchange(value = MqConst.EXCHANGE_DIRECT_SECKILL_USER),
            value = @Queue(value = MqConst.QUEUE_SECKILL_USER, durable = "true", autoDelete = "false"),
            key = (MqConst.ROUTING_SECKILL_USER)
    )})
    public void startSeckill(UserRecode userRecode, Message message, Channel channel) {
        try {
            //1.校验状态位
            Object status = CacheHelper.get(userRecode.getSkuId().toString());
            if (!"1".equals(status)) {
                //无货
                //应答 队列 删除此消息  不要再发过来了
                channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
                return;//微服务不能返回给MQ任何东西
            }
            //2、校验用户是否已经买过了      K：前缀 +  userId + ":" +skuId
            //2.1 setnx  分布式缓存那会儿学的
            //setnx(skuId,haha) : 如果此skuID已经存在了 返回值 false 不设置  已经来过了
            //setnx(skuId,haha) : 如果此skuID不存在 返回值 true 设置   没来过
            Boolean isSale = redisTemplate.opsForValue().setIfAbsent(RedisConst.SECKILL_USER +
                    userRecode.getUserId() + ":" + userRecode.getSkuId(), "");
            if (!isSale) {
                //此用户已经来过了,不能再买了   能走到这里说明有货,又因为还来过了,说明上次来已经抢到货了
                channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
                return;
            }
            //3.获取秒杀商品
            String skuId = (String) redisTemplate.opsForList().rightPop(RedisConst.SECKILL_STOCK_PREFIX + userRecode.getSkuId());
            if (StringUtils.isEmpty(skuId)) {
                //3.1 没有商品了 卖完了  抢购失败   更新状态位到每个秒杀微服务
                redisTemplate.convertAndSend("seckillpush", userRecode.getSkuId() + ":0");
                channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
                return;
            }
            //4. 有 抢购成功
            //4.1 从缓存中查询出秒杀商品信息
            SeckillGoods seckillGoods = (SeckillGoods) redisTemplate.opsForHash().
                    get(RedisConst.SECKILL_GOODS, userRecode.getSkuId().toString());
            //4.2:再保存当前用户的秒杀商品的资格   其实就相当于秒杀成功
            redisTemplate.opsForValue().set(RedisConst.SECKILL_ORDERS
                    + userRecode.getUserId() + ":" + userRecode.getSkuId(), seckillGoods);
            //5:..同步DB   善后工作  不是秒杀核心代码
            //应答
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
